//	--------------------------------------------------------------------
//	Copyright(C) 2006,2007 Zhao Yukun. All rights reserved.
//	This file is a part of the Xeres system.
//	Xeres 2007
//	Contact : xeres.engine@gmail.com
//	$(reservedInfo)
//	$(reservedDeclaration)
//	--------------------------------------------------------------------
///	@file	<input.cpp>
///	@path	~/src/gui/input/
///	@date	2007/09/23
///	@desc	Input base.

#include "config/config.h"

#include "lib/utilities/debug.h"

#include "gui/input/input.h"

#include <map>

namespace xeres {

#ifdef _DEBUG
	// Validate input
	void Input::Validate( void ) const
	{
		if( GetType() == TYPE_KEY )
		{
			// check action
			switch( GetAction() )
			{
			case ACTION_KEY_PRESS:
			case ACTION_KEY_RELEASE:
				// valid action
				break;
			default:
				// invalid input
				TRACE_ERROR( _S("Input::validate: Unexpected action in key input.") );
				FATAL_ABORT( _S("Input not validate.") );
			}
		}
		else if( GetType() == TYPE_MOUSE )
		{
			// check action
			switch( GetAction() )
			{
			case ACTION_MOUSE_LB_CLICK:
			case ACTION_MOUSE_MB_CLICK:
			case ACTION_MOUSE_RB_CLICK:
			case ACTION_MOUSE_LB_UP:
			case ACTION_MOUSE_MB_UP:
			case ACTION_MOUSE_RB_UP:
			case ACTION_MOUSE_LB_DBCLICK:
			case ACTION_MOUSE_RB_DBCLICK:
			case ACTION_MOUSE_WHEEL_UP:
			case ACTION_MOUSE_WHEEL_DOWN:
			case ACTION_MOUSE_MOVE:
				// valid action
				break;
			default:
				// invalid input
				TRACE_ERROR( _S("Input::validate: Unexpected action in mouse input.") );
				FATAL_ABORT( _S("Input not validate.") );
			}
		}
		else
		{
			// invalid input
			TRACE_ERROR( _S("Input::validate: Invalid input type.") );
			FATAL_ABORT( _S("Input not validate.") );
		}
	}
#endif /*_DEBUG*/

	namespace
	{
		// _KeyCharMapping
		class _KeyCharMapping
		{
		public:

			// instance
			static const _KeyCharMapping& instance( void )
			{
				static _KeyCharMapping s_keyCharMapping_instance;
				return s_keyCharMapping_instance;
			}

			// ctor
			_KeyCharMapping( void )
			{
#define	KEY2CHAR( KEY , CH , SHIFT , CAPS , NUM )	MapCharSet( KeyInput::KEY_##KEY## , CH , SHIFT , CAPS , NUM )

#define	DIG2CHAR( KEY , DIGI , SHIFT )				KEY2CHAR( KEY , DIGI , SHIFT , '\0' , '\0' )
#define	LET2CHAR( KEY , LETTER , SHIFT )			KEY2CHAR( KEY , LETTER , SHIFT , SHIFT , '\0' )
#define	PUN2CHAR( KEY , PUNC , SHIFT )				KEY2CHAR( KEY , PUNC , SHIFT , '\0' , '\0' )
#define	NUM2CHAR( KEY , NUM )						KEY2CHAR( KEY , '\0' , '\0' , '\0' , NUM )
#define	SPC2CHAR( KEY , CH )						KEY2CHAR( KEY , CH , '\0' , CH , CH )

				// digits
				DIG2CHAR( 1 , '1' , '!' );
				DIG2CHAR( 2 , '2' , '@' );
				DIG2CHAR( 3 , '3' , '#' );
				DIG2CHAR( 4 , '4' , '$' );
				DIG2CHAR( 5 , '5' , '%' );
				DIG2CHAR( 6 , '6' , '^' );
				DIG2CHAR( 7 , '7' , '&' );
				DIG2CHAR( 8 , '8' , '*' );
				DIG2CHAR( 9 , '9' , '(' );
				DIG2CHAR( 0 , '0' , ')' );
				// letters
				LET2CHAR( A , 'a' , 'A' );
				LET2CHAR( B , 'b' , 'B' );
				LET2CHAR( C , 'c' , 'C' );
				LET2CHAR( D , 'd' , 'D' );
				LET2CHAR( E , 'e' , 'E' );
				LET2CHAR( F , 'f' , 'F' );
				LET2CHAR( G , 'g' , 'G' );
				LET2CHAR( H , 'h' , 'H' );
				LET2CHAR( I , 'i' , 'I' );
				LET2CHAR( J , 'j' , 'J' );
				LET2CHAR( K , 'k' , 'K' );
				LET2CHAR( L , 'l' , 'L' );
				LET2CHAR( M , 'm' , 'M' );
				LET2CHAR( N , 'n' , 'N' );
				LET2CHAR( O , 'o' , 'O' );
				LET2CHAR( P , 'p' , 'P' );
				LET2CHAR( Q , 'q' , 'Q' );
				LET2CHAR( R , 'r' , 'R' );
				LET2CHAR( S , 's' , 'S' );
				LET2CHAR( T , 't' , 'T' );
				LET2CHAR( U , 'u' , 'U' );
				LET2CHAR( V , 'v' , 'V' );
				LET2CHAR( W , 'w' , 'W' );
				LET2CHAR( X , 'x' , 'X' );
				LET2CHAR( Y , 'y' , 'Y' );
				LET2CHAR( Z , 'z' , 'Z' );
				// punctuation
				PUN2CHAR( COMMA		, ',' , '<' );
				PUN2CHAR( PERIOD	, '.' , '>' );
				PUN2CHAR( LBRACKET	, '[' , '{' );
				PUN2CHAR( RBRACKET	, ']' , '}' );
				PUN2CHAR( GRAVE		, '`' , '~' );
				PUN2CHAR( MINUS		, '-' , '_' );
				PUN2CHAR( EQUAL		, '=' , '+' );
				PUN2CHAR( SEMICOLON	, ';' , ':' );
				PUN2CHAR( SLASH		, '/' , '?' );
				PUN2CHAR( BACKSLASH	, '\\' , '|' );
				PUN2CHAR( APOSTROPHE, '\'' , '"' );
				// special
				SPC2CHAR( TAB , '\t' );
				SPC2CHAR( RETURN , '\n' );
				// num pad
				NUM2CHAR( NUMPAD1 		, '1' );
				NUM2CHAR( NUMPAD2 		, '2' );
				NUM2CHAR( NUMPAD3 		, '3' );
				NUM2CHAR( NUMPAD4 		, '4' );
				NUM2CHAR( NUMPAD5 		, '5' );
				NUM2CHAR( NUMPAD6 		, '6' );
				NUM2CHAR( NUMPAD7 		, '7' );
				NUM2CHAR( NUMPAD8 		, '8' );
				NUM2CHAR( NUMPAD9 		, '9' );
				NUM2CHAR( NUMPAD0 		, '0' );
				NUM2CHAR( NUMPADMINUS	, '-' );
				NUM2CHAR( NUMPADADD		, '+' );
				NUM2CHAR( NUMPADPERIOD	, '.' );
				NUM2CHAR( NUMPADSTAR	, '*' );
				// space
				KEY2CHAR( SPACE		, ' ' , ' ' , ' ' , ' ' );
#undef	KEY2CHAR

#define	KEY2STR( KEY )		MapStr( KeyInput::KEY_##KEY## , L#KEY )

				KEY2STR( ESCAPE			);
				KEY2STR( 1          	);
				KEY2STR( 2          	);
				KEY2STR( 3          	);
				KEY2STR( 4          	);
				KEY2STR( 5          	);
				KEY2STR( 6          	);
				KEY2STR( 7          	);
				KEY2STR( 8          	);
				KEY2STR( 9          	);
				KEY2STR( 0          	);
				KEY2STR( MINUS      	);
				KEY2STR( EQUAL      	);
				KEY2STR( BACKSPACE  	);
				KEY2STR( TAB        	);
				KEY2STR( Q          	);
				KEY2STR( W          	);
				KEY2STR( E          	);
				KEY2STR( R          	);
				KEY2STR( T          	);
				KEY2STR( Y          	);
				KEY2STR( U          	);
				KEY2STR( I          	);
				KEY2STR( O          	);
				KEY2STR( P          	);
				KEY2STR( LBRACKET   	);
				KEY2STR( RBRACKET   	);
				KEY2STR( RETURN     	);
				KEY2STR( LCTRL		   	);
				KEY2STR( A          	);
				KEY2STR( S          	);
				KEY2STR( D          	);
				KEY2STR( F          	);
				KEY2STR( G          	);
				KEY2STR( H          	);
				KEY2STR( J          	);
				KEY2STR( K          	);
				KEY2STR( L          	);
				KEY2STR( SEMICOLON  	);
				KEY2STR( APOSTROPHE 	);
				KEY2STR( GRAVE      	);
				KEY2STR( LSHIFT     	);
				KEY2STR( BACKSLASH  	);
				KEY2STR( Z          	);
				KEY2STR( X          	);
				KEY2STR( C          	);
				KEY2STR( V          	);
				KEY2STR( B          	);
				KEY2STR( N          	);
				KEY2STR( M          	);
				KEY2STR( COMMA      	);
				KEY2STR( PERIOD     	);
				KEY2STR( SLASH      	);
				KEY2STR( RSHIFT     	);
				KEY2STR( NUMPADSTAR 	);
				KEY2STR( LALT       	);
				KEY2STR( SPACE      	);
				KEY2STR( CAPSLOCK   	);
				KEY2STR( F1         	);
				KEY2STR( F2         	);
				KEY2STR( F3         	);
				KEY2STR( F4         	);
				KEY2STR( F5         	);
				KEY2STR( F6         	);
				KEY2STR( F7         	);
				KEY2STR( F8         	);
				KEY2STR( F9         	);
				KEY2STR( F10        	);
				KEY2STR( PAUSE      	);
				KEY2STR( SCRLOCK     	);
				KEY2STR( NUMPAD7    	);
				KEY2STR( NUMPAD8    	);
				KEY2STR( NUMPAD9    	);
				KEY2STR( NUMPADMINUS	);
				KEY2STR( NUMPAD4    	);
				KEY2STR( NUMPAD5    	);
				KEY2STR( NUMPAD6    	);
				KEY2STR( NUMPADADD  	);
				KEY2STR( NUMPAD1    	);
				KEY2STR( NUMPAD2    	);
				KEY2STR( NUMPAD3    	);
				KEY2STR( NUMPAD0    	);
				KEY2STR( NUMPADPERIOD	);    
				KEY2STR( OEM_102    	);
				KEY2STR( F11        	);
				KEY2STR( F12        	);

				KEY2STR( F13        	);
				KEY2STR( F14        	);
				KEY2STR( F15        	);

				KEY2STR( KANA       	);
				KEY2STR( ABNT_C1    	);
				KEY2STR( CONVERT    	);
				KEY2STR( NOCONVERT  	);
				KEY2STR( YEN        	);
				KEY2STR( ABNT_C2    	);
				KEY2STR( NUMPADEQUALS	);    
				KEY2STR( PREVTRACK  	);
				KEY2STR( AT         	);
				KEY2STR( COLON      	);
				KEY2STR( UNDERLINE  	);
				KEY2STR( KANJI      	);
				KEY2STR( STOP       	);
				KEY2STR( AX         	);
				KEY2STR( UNLABELED  	);
				KEY2STR( NEXTTRACK  	);
				KEY2STR( NUMPADENTER	);
				KEY2STR( RCTRL		   	);
				KEY2STR( MUTE       	);
				KEY2STR( CALCULATOR 	);
				KEY2STR( PLAYPAUSE  	);
				KEY2STR( MEDIASTOP  	);
				KEY2STR( VOLUMEDOWN 	);
				KEY2STR( VOLUMEUP   	);
				KEY2STR( WEBHOME    	);
				KEY2STR( NUMPADCOMMA 	);
				KEY2STR( NUMPADSLASH 	);
				KEY2STR( SYSRQ      	);
				KEY2STR( RALT       	);
				KEY2STR( NUMLOCK    	);
				KEY2STR( HOME       	);
				KEY2STR( UPARROW    	);
				KEY2STR( PGUP       	);
				KEY2STR( LEFTARROW  	);
				KEY2STR( RIGHTARROW 	);
				KEY2STR( END        	);
				KEY2STR( DOWNARROW  	);
				KEY2STR( PGDN       	);
				KEY2STR( INSERT     	);
				KEY2STR( DELETE     	);
				KEY2STR( LWIN       	);
				KEY2STR( RWIN       	);
				KEY2STR( APPS       	);
				KEY2STR( POWER      	);
				KEY2STR( SLEEP      	);
				KEY2STR( WAKE       	);
				KEY2STR( WEBSEARCH  	);
				KEY2STR( WEBFAVORITES	);
				KEY2STR( WEBREFRESH 	);
				KEY2STR( WEBSTOP    	);
				KEY2STR( WEBFORWARD 	);
				KEY2STR( WEBBACK    	);
				KEY2STR( MYCOMPUTER 	);
				KEY2STR( MAIL       	);
				KEY2STR( MEDIASELECT	);
			}

			// Get character by key.
			wchar_t GetCharByKey( KeyInput::Key key , bool has_shift , bool has_caps , bool has_num ) const
			{
				std::map< KeyInput::Key , _CharSet >::const_iterator it =
					m_keyCharMap.find( key );
				if( it == m_keyCharMap.end() )
					return '\0';

				const _CharSet& cs = it->second;

				// Not a normal character?
				if( cs.charNormal == '\0' )
				{
					// Only affected by num-lock.
					return has_num ? cs.withNum : cs.charNormal;
				}

				// Not affected by CAPS lock?
				if( cs.withCaps == '\0' )
				{
					// Will affected by shift?
					if( cs.withShift == '\0' )
						return cs.charNormal;
					else
						// Shift or normal.
						return has_shift ? cs.withShift : cs.charNormal;
				}

				// CAPS lock & shift both affects.
				bool invert = has_caps;
				if( has_shift )
					invert = !invert;
				return invert ? cs.withShift : cs.charNormal;
			}

			// Get string by Key.
			const WString& GetStrByKey( KeyInput::Key key ) const
			{
				std::map< KeyInput::Key , WString >::const_iterator it =
					m_keyStrMap.find( key );
				if( it == m_keyStrMap.end() )
				{
					static const WString s_errStr( _S("<error>") );
					return s_errStr;
				}
				return it->second;
			}

		private:

			// make set
			void MapCharSet( KeyInput::Key key , wchar_t ch , wchar_t shift , wchar_t caps , wchar_t num )
			{
				_CharSet cs;
				cs.charNormal = ch;
				cs.withShift = shift;
				cs.withCaps = caps;
				cs.withNum = num;
				m_keyCharMap[ key ] = cs;
			}

			// make string map
			void MapStr( KeyInput::Key key , const WString& str )
			{
				m_keyStrMap[ key ] = str;
			}

			// map char to key
			struct _CharSet
			{
				wchar_t		charNormal;
				wchar_t		withShift;
				wchar_t		withCaps;
				wchar_t		withNum;
			};

			std::map< KeyInput::Key , _CharSet >	m_keyCharMap;
			std::map< KeyInput::Key , WString >		m_keyStrMap;
		};
	}

	// get input char
	wchar_t KeyInput::GetInputChar( void ) const
	{
		return _KeyCharMapping::instance().GetCharByKey(
			GetKey() , IsShiftDown() , IsCapsLocked() , IsNumLocked() );
	}
	
	// get input string
	const WString& KeyInput::GetInputString( void ) const
	{
		return ( _KeyCharMapping::instance().GetStrByKey( GetKey() ) );
	}

	// GetInputString
	const WString& KeyInput::GetInputString( Key key )
	{
		return ( _KeyCharMapping::instance().GetStrByKey( key ) );
	}

	// InputTranslator
	InputTranslator::InputTranslator( void )
		: m_inputState( 0 )
		, m_capturing( false )
	{
		m_lastPos.x = -1;
		m_lastPos.y = -1;
	}

	// TranslateMessage
	HandleInput InputTranslator::TranslateMessage( HWND hWnd , UINT message , WPARAM wParam , LPARAM lParam , InputMsg * input )
	{
		return ProcessMessage( hWnd , message , wParam , lParam , input );
	}
	// ProcessMessage
	HandleInput InputTranslator::ProcessMessage( HWND hWnd , UINT message , WPARAM wParam , LPARAM lParam , InputMsg * input )
	{
		switch( message )
		{
		case WM_ACTIVATE:
			
			RefreshStates( hWnd );
			// Not handled, turn to next filter.
			return INPUT_UNHANDLED;

		case WM_SYSKEYDOWN:
		case WM_KEYDOWN:

			// Process key down message.
			return ProcessKeyInput( true , wParam , lParam , input );

		case WM_SYSKEYUP:
		case WM_KEYUP:

			// Process key up message.
			return ProcessKeyInput( false , wParam , lParam , input );

		case WM_LBUTTONDOWN:
		case WM_MBUTTONDOWN:
		case WM_RBUTTONDOWN:
		case WM_LBUTTONUP:
		case WM_MBUTTONUP:
		case WM_RBUTTONUP:
		case WM_LBUTTONDBLCLK:
//		case WM_MBUTTONDBLCLK:		// NOTICE! Using this type of operation is not a good idea.
		case WM_RBUTTONDBLCLK:
		case WM_MOUSEMOVE:
		case WM_MOUSEWHEEL:

			// Process mouse message.
			return ProcessMouseInput( hWnd , message , wParam , lParam , input );
		}
		return INPUT_UNHANDLED;
	}
	// RefreshStates
	void InputTranslator::RefreshStates( HWND hWnd )
	{
		// Update states & mouse position.
		RECT rect;
		::GetWindowRect( hWnd , &rect );
		POINT pt;
		::GetCursorPos( &pt );
		m_lastPos.x = (SHORT)(pt.x - rect.left);
		m_lastPos.y = (SHORT)(pt.y - rect.top);

		// Get key states.
#define	KEY_STATE( VK , KEY )	if( HIWORD( ::GetKeyState( VK ) ) )	\
								{ m_inputState |= Input::INPUT_STATE_##KEY##; } \
								else { m_inputState &= ~Input::INPUT_STATE_##KEY##; }

		KEY_STATE( VK_LSHIFT , LSHIFT );
		KEY_STATE( VK_RSHIFT , RSHIFT );
		KEY_STATE( VK_LCONTROL , LCTRL );
		KEY_STATE( VK_RCONTROL , RCTRL );
		KEY_STATE( VK_LMENU , LALT );
		KEY_STATE( VK_RMENU , RALT );
#undef KEY_STATE

#define	KEY_STATE( VK , KEY )	if( LOWORD( ::GetKeyState( VK ) ) )	\
								{ m_inputState |= Input::INPUT_STATE_##KEY##; } \
								else { m_inputState &= ~Input::INPUT_STATE_##KEY##; }

		KEY_STATE( VK_CAPITAL , CAPSLOCK );
		KEY_STATE( VK_NUMLOCK , NUMLOCK );
		KEY_STATE( VK_SCROLL , SCRLOCK );
#undef KEY_STATE
	}
	// ProcessKeyInput
	HandleInput InputTranslator::ProcessKeyInput( bool press , WPARAM wParam , LPARAM lParam , InputMsg * input )
	{
		bool extended = ( lParam >> 24) & 1;
		bool repeat   = ((lParam >> 30) & 1) & press;

		KeyInput::Key vk = static_cast<KeyInput::Key>( ((DWORD)lParam >> 16) & 0x7F );
		if ( extended )
			vk = (KeyInput::Key)((DWORD)vk | 0x80);

		uint tag = repeat ? Input::INPUT_STATE_KEY_REPEAT : 0;
		switch( vk )
		{
		case KeyInput::KEY_LCTRL:
			tag |= Input::INPUT_STATE_LCTRL;
			break;
		case KeyInput::KEY_RCTRL:
			tag |= Input::INPUT_STATE_RCTRL;
			break;
		case KeyInput::KEY_LALT:
			tag |= Input::INPUT_STATE_LALT;
			break;
		case KeyInput::KEY_RALT:
			tag |= Input::INPUT_STATE_RALT;
			break;
		case KeyInput::KEY_LSHIFT:
			tag |= Input::INPUT_STATE_LSHIFT;
			break;
		case KeyInput::KEY_RSHIFT:
			tag |= Input::INPUT_STATE_RSHIFT;
			break;
#define	KEY_STATE( VK , KEY )	if( LOWORD( ::GetKeyState( VK ) ) )	\
								{ m_inputState |= Input::INPUT_STATE_##KEY##; } \
								else { m_inputState &= ~Input::INPUT_STATE_##KEY##; } break;

		case KeyInput::KEY_CAPSLOCK:
			KEY_STATE( VK_CAPITAL , CAPSLOCK );
		case KeyInput::KEY_NUMLOCK:
			KEY_STATE( VK_NUMLOCK , NUMLOCK );
		case KeyInput::KEY_SCRLOCK:
			KEY_STATE( VK_SCROLL , SCRLOCK );

#undef  KEY_STATE
		}
		if( tag )
		{
			if( press )
				m_inputState |= tag;
			else
				m_inputState &= ~tag;
		}
		if( repeat )
			m_inputState |= Input::INPUT_STATE_KEY_REPEAT;
		else
			m_inputState &= ~Input::INPUT_STATE_KEY_REPEAT;

		new (input) KeyInput( vk , press , m_inputState );
		return INPUT_HANDLED;
	}
	// ProcessMouseInput
	HandleInput InputTranslator::ProcessMouseInput( HWND hWnd , UINT message , WPARAM wParam , LPARAM lParam , InputMsg * input )
	{
		Input::Action action;
		POINTS pts = MAKEPOINTS( lParam );

		switch( message )
		{
		case WM_LBUTTONDOWN:
			action = Input::ACTION_MOUSE_LB_CLICK;
			m_inputState |= Input::INPUT_STATE_LB_DOWN;
			::SetCapture( hWnd );
			break;
		case WM_MBUTTONDOWN:
			action = Input::ACTION_MOUSE_MB_CLICK;
			m_inputState |= Input::INPUT_STATE_MB_DOWN;
			::SetCapture( hWnd );
			break;
		case WM_RBUTTONDOWN:
			action = Input::ACTION_MOUSE_RB_CLICK;
			m_inputState |= Input::INPUT_STATE_RB_DOWN;
			::SetCapture( hWnd );
			break;
		case WM_LBUTTONUP:
			action = Input::ACTION_MOUSE_LB_UP;
			m_inputState &= ~Input::INPUT_STATE_LB_DOWN;
			::ReleaseCapture();
			break;
		case WM_MBUTTONUP:
			action = Input::ACTION_MOUSE_MB_UP;
			m_inputState &= ~Input::INPUT_STATE_MB_DOWN;
			::ReleaseCapture();
			break;
		case WM_RBUTTONUP:
			action = Input::ACTION_MOUSE_RB_UP;
			m_inputState &= ~Input::INPUT_STATE_RB_DOWN;
			::ReleaseCapture();
			break;
		case WM_LBUTTONDBLCLK:
			action = Input::ACTION_MOUSE_LB_DBCLICK;
			break;
		case WM_RBUTTONDBLCLK:
			action = Input::ACTION_MOUSE_RB_DBCLICK;
			break;
		case WM_MOUSEMOVE:
			action = Input::ACTION_MOUSE_MOVE;
			break;
		case WM_MOUSEWHEEL:
			short delta = HIWORD(wParam);
			if( delta > 0 )
				action = Input::ACTION_MOUSE_WHEEL_UP;
			else
				action = Input::ACTION_MOUSE_WHEEL_DOWN;
			break;
		}

		new (input) MouseInput( pts.x , pts.y , action , m_inputState );
		m_lastPos = pts;

		return INPUT_HANDLED;
	}

	// InputDispatcher
	InputDispatcher::InputDispatcher( void )
	{
	}
	// DispatchMessage
	HandleInput InputDispatcher::DispatchMessage( HWND hWnd , UINT message , WPARAM wParam , LPARAM lParam )
	{
		InputMsg msg;
		if( ProcessMessage( hWnd , message , wParam , lParam , &msg ) == INPUT_UNHANDLED )
			return INPUT_UNHANDLED;

		if( msg.GetInput().IsKeyInput() )
			return OnKeyInput( msg.GetKeyInput() );
		else
			return OnMouseInput( msg.GetMouseInput() );
	}
	
} // namespace xeres
